/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.inputmethod.pinyin;

import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.PopupWindow;


import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.github.rockerhieu.emojicon.EmojiconGridFragment;
import io.github.rockerhieu.emojicon.EmojiconGridView;
import io.github.rockerhieu.emojicon.EmojiconPage;
import io.github.rockerhieu.emojicon.EmojiconsFragment;
import io.github.rockerhieu.emojicon.EmojiconsView;
import io.github.rockerhieu.emojicon.emoji.Emojicon;

/**
 * Main class of the Pinyin input method.
 */
public class PinyinIME extends InputMethodService implements
        EmojiconGridFragment.OnEmojiconClickedListener,View.OnClickListener{
    /**
     * TAG for debug.
     */
    static final String TAG = "PinyinIME";
    /**
     * Necessary environment configurations like screen size for this IME.
     */
    private Environment mEnvironment;

    /**
     * Used to switch input mode.
     */
    public InputModeSwitcher mInputModeSwitcher;

    /**
     * Soft keyboard container view to host real soft keyboard view.
     */
    public SkbContainer mSkbContainer;
    public LinearLayout mContainer;

    /**
     * The floating container which contains the composing view. If necessary,
     * some other view like candiates container can also be put here.
     */
    private LinearLayout mFloatingContainer;

    /**
     * View to show the composing string.
     */
    private ComposingView mComposingView;

    /**
     * Window to show the composing string.
     */
    private PopupWindow mFloatingWindow;

    /**
     * Used to show the floating window.
     */
    private PopupTimer mFloatingWindowTimer = new PopupTimer();

    /**
     * View to show candidates list.
     */
    private CandidatesContainer mCandidatesContainer;


    /**
     * Used to notify the input method when the user touch a candidate.
     */
    private ChoiceNotifier mChoiceNotifier;

    /**
     * Used to notify gestures from soft keyboard.
     */
    private OnGestureListener mGestureListenerSkb;

    /**
     * Used to notify gestures from candidates view.
     */
    private OnGestureListener mGestureListenerCandidates;

    /**
     * The on-screen movement gesture detector for soft keyboard.
     */
    private GestureDetector mGestureDetectorSkb;

    /**
     * The on-screen movement gesture detector for candidates view.
     */
    private GestureDetector mGestureDetectorCandidates;

    /**
     * Option dialog to choose settings and other IMEs.
     */
    private AlertDialog mOptionsDialog;
    /**
     * The current IME status.
     *
     * @see com.android.inputmethod.pinyin.PinyinIME.ImeState
     */
    public static ImeState mImeState = ImeState.STATE_IDLE;



    public static final int MAX_CANDS = 300;
    public static final int MAX_SPL_NUM = 24;

    // receive ringer mode changes
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            SoundManager.getInstance(context,getApplication()).updateRingerMode();
        }
    };

    @Override
    public void onCreate() {
        mEnvironment = Environment.getInstance();
        super.onCreate();
        mInputModeSwitcher = new InputModeSwitcher(this);
        mChoiceNotifier = new ChoiceNotifier(this);
        mGestureListenerSkb = new OnGestureListener(false);
        mGestureListenerCandidates = new OnGestureListener(true);
        mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);
        mGestureDetectorCandidates = new GestureDetector(this,
                mGestureListenerCandidates);

        mEnvironment.onConfigurationChanged(getResources().getConfiguration(),
                this);

    }

    @Override
    public void onDestroy() {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onDestroy.");
        }
        Settings.releaseInstance();
        super.onDestroy();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        Environment env = Environment.getInstance();
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onConfigurationChanged");
            Log.d(TAG, "--last config: " + env.getConfiguration().toString());
            Log.d(TAG, "---new config: " + newConfig.toString());
        }
        // We need to change the local environment first so that UI components
        // can get the environment instance to handle size issues. When
        // super.onConfigurationChanged() is called, onCreateCandidatesView()
        // and onCreateInputView() will be executed if necessary.
        env.onConfigurationChanged(newConfig, this);

        // Clear related UI of the previous configuration.
        if (null != mSkbContainer) {
            mSkbContainer.dismissPopups();
        }
        super.onConfigurationChanged(newConfig);
        resetToIdleState();
    }

    @Override
    public View onCreateCandidatesView() {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onCreateCandidatesView.");
        }

        LayoutInflater inflater = getLayoutInflater();
        // Inflate the floating container view
        mFloatingContainer = (LinearLayout) inflater.inflate(
                R.layout.floating_container, null);

        // The first child is the composing view.
        mComposingView = (ComposingView) mFloatingContainer.getChildAt(0);

        // The floating window
        if (null != mFloatingWindow && mFloatingWindow.isShowing()) {
            mFloatingWindowTimer.cancelShowing();
            mFloatingWindow.dismiss();
        }
        mFloatingWindow = new PopupWindow(this);
        mFloatingWindow.setClippingEnabled(false);
        mFloatingWindow.setBackgroundDrawable(null);
        mFloatingWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
        mFloatingWindow.setContentView(mFloatingContainer);

        return null;
    }

    private void processPinyin(String keyText)
    {
        keyText = keyText.toLowerCase();
        char[] keyChars = keyText.toCharArray();
        if(mImeState == ImeState.STATE_INPUT)
        {
            if(mSplString.length() >= MAX_SPL_NUM) return;
            mSplString += keyChars[0];
            chooseAndUpdate(-1);
        }else {
            mSplString = ""+keyChars[0];
            chooseAndUpdate(-1);
        }

    }

    public int[] currentDicts = {R.raw.dict_cn,R.raw.common_dict};
    public boolean isQwer = false;
    public TrieTree tree = null;
    Map<String,TrieTree> treeMap = new HashMap<>();
    public void buildTree(TrieTree tree,int dict,boolean qwer)
    {
        InputStream inputStream = getResources().openRawResource(dict);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader reader = new BufferedReader(inputStreamReader);
        long m = System.currentTimeMillis();
        try
        {
            String line = "";
            while ((line = reader.readLine()) != null)
            {
                String[] strings = line.split(" ");
                if(strings.length < 4) continue;
                Letter l = new Letter(strings[0],strings[1],Float.parseFloat(strings[2]),strings[3]);
                if(qwer)
                    tree.addLetter(l,l.splString);
                else
                    tree.addLetter(l,l.numberSplString);
            }
        }catch (Exception e)
        {
            e.printStackTrace();
        }finally {
            try
            {
                inputStream.close();
                inputStreamReader.close();
                reader.close();
                inputStream = null;
                inputStreamReader = null;
                reader = null;
            }catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }


    static String mSplString  = "";
    static String mSplStringForDispaly  = "";
    List<Letter> mCandLetters;
    int layer = 4;
    public List<Letter> getCandLettersList() throws Exception
    {
        if(tree == null) return null;
        List<Letter> letters = new ArrayList<>();
        letters = tree.getAllSubLetters(letters,mSplString,layer,MAX_CANDS);
        if(InputModeSwitcher.LuaState.JP == mInputModeSwitcher.state)
            letters.sort(PinyinIME.letterLitterComparator);
        else
            letters.sort(PinyinIME.letterBigComparator);

        mSplStringForDispaly = getDisplaySpl(letters,mSplString);
        if(letters.size() == 0)
        {
            letters.add(new Letter("",mSplStringForDispaly,0,""));
        }
        return letters;
    }

    private String getDispalyNumCode(String spl)
    {
        String numCode = "";
        for(int i=0;i<spl.length();i++)
        {
            char c = spl.charAt(i);
            switch (c)
            {
                case 'a':
                    numCode += "2";
                    break;
                case 'd':
                    numCode += "3";
                    break;
                case 'g':
                    numCode += "4";
                    break;
                case 'j':
                    numCode += "5";
                    break;
                case 'm':
                    numCode += "6";
                    break;
                case 'p':
                    numCode += "7";
                    break;
                case 't':
                    numCode += "8";
                    break;
                case 'w':
                    numCode += "9";
                    break;
                default:
                    numCode += c;
                    break;
            }
        }
        return  numCode;
    }

    private String getDisplaySpl(List<Letter> letters,String mSplString)
    {
        String display = mSplString;
        if(letters == null || letters.size() == 0) return getDispalyNumCode(mSplString);
        Letter letter = letters.get(0);
        if(mSplString.length() <= letter.splString.length())
        {
            display = letter.splString.substring(0,mSplString.length());
        }
        return display;
    }


    private boolean processNormalKey(String keyText,String upperText) {
        int keyChar = 0;
        if(keyText == null) return false;
        keyChar = keyText.charAt(0);
        if(mInputModeSwitcher.state == InputModeSwitcher.LuaState.EN
                && !Settings.getEnDict()
                && mInputModeSwitcher.keyBoardState == InputModeSwitcher.KeyBoardState.FULL_KEY)
        {
            if(!InputModeSwitcher.KeyBoards.CAPS)
                keyChar = keyText.toLowerCase().charAt(0);
            commitResultText(String.valueOf((char) keyChar));
            return true;
        }
        if (keyChar >= 'a' && keyChar <= 'z') {
            processPinyin(keyText);
            return true;
        }else if(keyChar >= 'A' && keyChar <= 'Z') {
            processPinyin(keyText);
            return true;
        }else if(keyChar >= '0' && keyChar <= '9'){

                if(mInputModeSwitcher.state == InputModeSwitcher.LuaState.NUM)
                {
                    commitResultText(String.valueOf((char) keyChar));
                }else if(mInputModeSwitcher.state == InputModeSwitcher.LuaState.CN)
                {
                    if(keyChar == '0' || keyChar == '1')
                    {
                        processPinyin(keyText);
                        return true;
                    }
                    commitResultText(String.valueOf((char) keyChar));
                }if(mInputModeSwitcher.state == InputModeSwitcher.LuaState.EN)
                {

                }if(mInputModeSwitcher.state == InputModeSwitcher.LuaState.JP)
                {
                    if(keyChar == '0' || keyChar == '1')
                    {
                        processPinyin(keyText);
                        return true;
                    }
                    commitResultText(String.valueOf((char) keyChar));
                }if(mInputModeSwitcher.state == InputModeSwitcher.LuaState.EN)
                {
                    if(keyChar == '0' || keyChar == '1')
                    {
                        processPinyin(keyText);
                        return true;
                    }
                    commitResultText(String.valueOf((char) keyChar));
                }
                if(mImeState == ImeState.STATE_INPUT)
                {
                    chooseCandidate(-1);
                }
            }else {
                if(mImeState == ImeState.STATE_INPUT)
                {
                    chooseCandidate(-1);
                }
                if (0 != keyChar) {
                    commitResultText(keyText);
                    return true;
                }
            }
        return false;
    }

    final String KEY_BACK = "KEY_BACK";
    final String KEY_ENTER = "KEY_ENTER";
    final String KEY_SPACE = "KEY_SPACE";
    final String KEY_FUHAO = "KEY_FUHAO";
    final String KEY_DEL = "KEY_DEL";
    final String KEY_NUM = "KEY_NUM";
    final String KEY_RESET = "KEY_RESET";
    final String KEY_EN = "KEY_EN";
    final String KEY_JP = "KEY_JP";
    final String KEY_CN = "KEY_CN";
    final String KEY_CAPS = "KEY_CAPS";
    final String KEY_NINE_KEY = "KEY_NINE_KEY";
    final String KEY_FULL_KEY = "KEY_FULL_KEY";
    private boolean processFunctionKeys(String keyTag, boolean realAction) {
        // Back icon_setting is used to dismiss all popup UI in a soft keyboard.
        if(keyTag.equals(KEY_RESET)) {
            resetToIdleState();
            return true;
        }else if(keyTag.equals(KEY_NINE_KEY)) {
            resetToIdleState();
            if (isInputViewShown()) {
                if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                mInputModeSwitcher.changeKeyBoard(mInputModeSwitcher.lastState,
                        InputModeSwitcher.KeyBoardState.NINE_KEY);
                return true;
            }
            return true;
        }else if(keyTag.equals(KEY_FULL_KEY)) {
            resetToIdleState();
            if (isInputViewShown()) {
                if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                mInputModeSwitcher.changeKeyBoard(mInputModeSwitcher.lastState,
                        InputModeSwitcher.KeyBoardState.FULL_KEY);
                return true;
            }
            return true;
        }else if(keyTag.equals(KEY_CAPS)) {
            InputModeSwitcher.KeyBoards.CAPS = !InputModeSwitcher.KeyBoards.CAPS;
            return true;
        }else if (keyTag.equals(KEY_NUM)) {
                if (isInputViewShown()) {
                    if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                    mInputModeSwitcher.changeKeyBoard(InputModeSwitcher.LuaState.NUM,null);
                    return true;
                }
            }else if (keyTag.equals(KEY_JP)) {
                if (isInputViewShown()) {
                    if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                    mInputModeSwitcher.changeKeyBoard(InputModeSwitcher.LuaState.JP,
                            mInputModeSwitcher.keyBoardState);
                    return true;
                }
            }else if (keyTag.equals(KEY_EN)) {
                if (isInputViewShown()) {
                    if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                    mInputModeSwitcher.changeKeyBoard(InputModeSwitcher.LuaState.EN,
                            mInputModeSwitcher.keyBoardState);
                    return true;
                }
            }else if (keyTag.equals(KEY_CN)) {
                if (isInputViewShown()) {
                    if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                    mInputModeSwitcher.changeKeyBoard(InputModeSwitcher.LuaState.CN,
                            mInputModeSwitcher.keyBoardState);
                    return true;
                }
            }else if (keyTag.equals(KEY_FUHAO)) {
                if (isInputViewShown()) {
                    if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                    mInputModeSwitcher.changeKeyBoard(InputModeSwitcher.LuaState.SYM,null);
                return true;
                }
            }else if (keyTag.equals(KEY_BACK)) {
                if (isInputViewShown()) {
                    if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                    mInputModeSwitcher.changeKeyBoard(mInputModeSwitcher.lastState,
                            mInputModeSwitcher.keyBoardState);
                    return true;
                }
            }else if(keyTag.equals(KEY_FUHAO))
            {
                if(mImeState == ImeState.STATE_INPUT) resetToIdleState();
                return true;
            }


            if (keyTag.equals(KEY_DEL)) {
                if (ImeState.STATE_PREDICT == mImeState) {
                    resetToIdleState();
                    return true;
                }else if(mImeState == ImeState.STATE_INPUT)
                {
                    if(mSplString.length()>0)
                        mSplString = mSplString.substring(0,mSplString.length()-1);
                    if(mSplString.length() == 0)
                    {
                        resetToIdleState();
                        return true;
                    }
                    chooseAndUpdate(-1);

                    return true;
                }
                getCurrentInputConnection().deleteSurroundingText(1, 0);
                return true;
            }
            if (keyTag.equals(KEY_ENTER)) {
                if (!realAction) return true;
                if(mImeState == ImeState.STATE_INPUT || mImeState == ImeState.STATE_PREDICT){
                    commitResultText(mSplStringForDispaly);
                    resetToIdleState();
                    return true;
                }
                sendKeyChar('\n');
                return true;
            }
            if (keyTag.equals(KEY_SPACE)) {
                if (!realAction) return true;
                if(mImeState == ImeState.STATE_INPUT || mImeState == ImeState.STATE_PREDICT){
                    chooseCandidate(-1);
                    return true;
                }
                sendKeyChar(' ');
                return true;
            }

        return false;
    }

    private void changeToStateInput() {
        mImeState = ImeState.STATE_INPUT;
        showCandidateWindow(true);
    }

    public void commitResultText(String resultText) {
        InputConnection ic = getCurrentInputConnection();
        if (null != ic) ic.commitText(resultText, 1);
        if (null != mComposingView) {
            mComposingView.setVisibility(View.INVISIBLE);
            mComposingView.invalidate();
        }
    }

    private void updateComposingText(boolean visible) {
        if (!visible) {
            mComposingView.setVisibility(View.INVISIBLE);
        } else {
            mComposingView.setDecodingInfo(mCandLetters, mImeState);
            mComposingView.setVisibility(View.VISIBLE);
        }
        mComposingView.invalidate();
    }

    public void resetToIdleState() {
        if (ImeState.STATE_IDLE == mImeState) return;
        mCandLetters.clear();
        mImeState = ImeState.STATE_IDLE;
        mSplString = "";

        if (null != mComposingView) mComposingView.reset();
        resetCandidateWindow();
    }

    private void chooseAndUpdate(int candId) {
        if(candId < 0)
        {
            if(mSplString.length() == 0)
            {
                mCandLetters.clear();
                return;
            }
            try {
                mCandLetters = getCandLettersList();
            }catch (Exception e)
            {
                e.printStackTrace();
            }
            changeToStateInput();
        }else
        {
            if(mCandLetters == null || mCandLetters.size() <= candId)
            {
                return;
            }
            Letter choice = mCandLetters.get(candId);
            if(choice != null) commitResultText(choice.letter);
            resetToIdleState();
            return;
        }
    }

    // If activeCandNo is less than 0, get the current active candidate number
    // from candidate view, otherwise use activeCandNo.
    public void chooseCandidate(int activeCandNo) {
        if (activeCandNo < 0) {
            activeCandNo = mCandidatesContainer.getActiveCandiatePos();
        }
        if (activeCandNo >= 0) {
            chooseAndUpdate(activeCandNo);
        }
    }



    final String FUNCTION = "KeyBoard_Function";
    final String NORMAL = "KeyBoard_Normal";

    public void responseSoftKeyEvent(View sKey) {
        if (null == sKey) return;

        InputConnection ic = getCurrentInputConnection();
        if (ic == null) return;

        String keyTag = (String)sKey.getTag();
        String keyText = "";
        String upperText = "";
        if(sKey instanceof NineKeyButton)
        {
            keyText = ((NineKeyButton)sKey).getText().toString();
            upperText = ((NineKeyButton)sKey).upperText;
        }
        String keyDesc = (String)sKey.getContentDescription();
        // Process some general keys, including KEYCODE_DEL, KEYCODE_SPACE,
        // KEYCODE_ENTER and KEYCODE_DPAD_CENTER.
        if (keyDesc.equals(FUNCTION)) {
            if (processFunctionKeys(keyTag, true)) return;
        }else if(keyDesc.equals(NORMAL))
        {
            processNormalKey(keyText,upperText);
        }

    }



    private void showCandidateWindow(boolean showComposingView) {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "Candidates window is shown. Parent = "
                    + mCandidatesContainer);
        }


        setCandidatesViewShown(true);

        if (null != mSkbContainer) mSkbContainer.requestLayout();

        if (null == mCandidatesContainer) {
            resetToIdleState();
            return;
        }

        updateComposingText(showComposingView);
        mCandidatesContainer.showCandidates(mCandLetters,
                true);
        mFloatingWindowTimer.postShowFloatingWindow();
    }

    private void dismissCandidateWindow() {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "Candidates window is to be dismissed");
        }
        if (null == mCandidatesContainer) return;
        try {
            mFloatingWindowTimer.cancelShowing();
            mFloatingWindow.dismiss();
        } catch (Exception e) {
            Log.e(TAG, "Fail to show the PopupWindow.");
        }
        setCandidatesViewShown(true);

        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.toggleCandidateMode(false);
        }
    }

    private void resetCandidateWindow() {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "Candidates window is to be reset");
        }
        if (null == mCandidatesContainer) return;
        try {
            mFloatingWindowTimer.cancelShowing();
            mFloatingWindow.dismiss();
        } catch (Exception e) {
            Log.e(TAG, "Fail to show the PopupWindow.");
        }

        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.toggleCandidateMode(false);
        }

        if (null != mCandidatesContainer && mCandidatesContainer.isShown()) {
            showCandidateWindow(false);
        }
    }

    @Override
    public View onCreateInputView() {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onCreateInputView.");
        }
        LayoutInflater inflater = getLayoutInflater();
        mContainer = (LinearLayout) inflater.inflate(R.layout.skb_container,
                null);
        mSkbContainer = (SkbContainer) mContainer.findViewById(R.id.SkbContainer);
        mCandidatesContainer = (CandidatesContainer) mContainer.findViewById(R.id.candidates_container);
        mCandidatesContainer.setServiceInstance(this);
        mCandidatesContainer.initialize(mChoiceNotifier,mGestureDetectorCandidates);
        mSkbContainer.setService(this);
        mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);
        mSkbContainer.setGestureDetector(mGestureDetectorSkb);
        return mContainer;
    }

    @Override
    public void onStartInput(EditorInfo editorInfo, boolean restarting) {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onStartInput " + " ccontentType: "
                    + String.valueOf(editorInfo.inputType) + " Restarting:"
                    + String.valueOf(restarting));
        }
        resetToIdleState();
    }
    public static Comparator<Letter> letterBigComparator = new Comparator<Letter>() {
        @Override
        public int compare(Letter o1, Letter o2) {
            if(o1.freq == o2.freq) return 0;
            return o1.freq > o2.freq?-1:1;
        }
    };
    public static Comparator<Letter> letterLitterComparator = new Comparator<Letter>() {
        @Override
        public int compare(Letter o1, Letter o2) {
            if(o1.freq == o2.freq) return 0;
            return o1.freq > o2.freq?1:-1;
        }
    };

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if(keyCode == KeyEvent.KEYCODE_BACK && isInputViewShown() && mInputModeSwitcher.state != mInputModeSwitcher.lastState)
        {
            mInputModeSwitcher.changeKeyBoard(mInputModeSwitcher.lastState,mInputModeSwitcher.keyBoardState);
            return true;
        }
        return super.onKeyUp(keyCode,event);
    }

    @Override
    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onStartInputView " + " contentType: "
                    + String.valueOf(editorInfo.inputType) + " Restarting:"
                    + String.valueOf(restarting));
        }

        Settings.getInstance(PreferenceManager
                .getDefaultSharedPreferences(getApplicationContext()));

        resetToIdleState();
        mInputModeSwitcher.changeKeyBoard(mInputModeSwitcher.lastState,
               mInputModeSwitcher.keyBoardState);
        setCandidatesViewShown(true);

    }
    int[] cn_dicts = {InputModeSwitcher.Dict.CN,InputModeSwitcher.Dict.COMMON};
    int[] en_dicts = {InputModeSwitcher.Dict.EN};
    int[] jp_dicts = {InputModeSwitcher.Dict.COMMON,InputModeSwitcher.Dict.JP};

    public void buildAllDicts()
    {
        buildDict(cn_dicts,true);
        buildDict(cn_dicts,false);
        buildDict(jp_dicts,true);
        buildDict(jp_dicts,false);
        buildDict(en_dicts,true);
        buildDict(en_dicts,false);
    }

    public void buildDict(int[] dicts,boolean isQwer)
    {
        TrieTree tempTree;
        if((tempTree = treeMap.get(sumInt(dicts)+""+isQwer)) == null)
        {
            tempTree = new TrieTree();
            int ids = 0;
            for(int id:currentDicts)
            {
                ids += id;
            }
            treeMap.put(ids+""+isQwer,tempTree);
            for(int id:dicts)
            {
                buildTree(tempTree,id,isQwer);
            }
        }
    }

    public int sumInt(int[] ints)
    {
        int sum = 0;
        for(int i:ints)
        {
            sum += i;
        }
        return sum;
    }
    public void createLoadingView()
    {
        mSkbContainer.updateSkbLayout(R.layout.keyboard_loading);
    }

    @Override
    public void onFinishInputView(boolean finishingInput) {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onFinishInputView.");
        }
        resetToIdleState();
        super.onFinishInputView(finishingInput);
    }

    @Override
    public void onFinishInput() {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "onFinishInput.");
        }
        resetToIdleState();
        super.onFinishInput();
    }



    @Override public void onDisplayCompletions(CompletionInfo[] completions) {
        if (!isFullscreenMode()) return;
        if (null == completions || completions.length <= 0) return;
        if (null == mSkbContainer || !mSkbContainer.isShown()) return;

        if (!mInputModeSwitcher.isChineseText() ||
                ImeState.STATE_IDLE == mImeState ||
                ImeState.STATE_PREDICT == mImeState) {
//            mImeState = ImeState.STATE_APP_COMPLETION;
            showCandidateWindow(false);
        }
    }

    private void onChoiceTouched(int activeCandNo) {
        if (mImeState == ImeState.STATE_INPUT
                || mImeState == ImeState.STATE_PREDICT) {
            chooseCandidate(activeCandNo);
        }
    }


    @Override
    public void requestHideSelf(int flags) {
        if (mEnvironment.needDebug()) {
            Log.d(TAG, "DimissSoftInput.");
        }
        dismissCandidateWindow();
        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.dismissPopups();
        }
        super.requestHideSelf(flags);
    }

    public void showOptionsMenu() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setCancelable(true);
        builder.setIcon(R.drawable.app_icon);
        builder.setNegativeButton(android.R.string.cancel, null);
        CharSequence itemSettings = getString(R.string.ime_settings_activity_name);
        CharSequence itemInputMethod = "zh_CN";
        builder.setItems(new CharSequence[] {itemSettings, itemInputMethod},
                new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface di, int position) {
                        di.dismiss();
                        switch (position) {
                        case 0:
                            launchSettings();
                            break;
                        case 1:
                            InputMethodManager imm = (InputMethodManager)
                                    getSystemService(Context.INPUT_METHOD_SERVICE);
                            imm.showInputMethodPicker();
                            break;
                        }
                    }
                });
        builder.setTitle(getString(R.string.ime_name));
        mOptionsDialog = builder.create();
        Window window = mOptionsDialog.getWindow();
        WindowManager.LayoutParams lp = window.getAttributes();
        lp.token = mSkbContainer.getWindowToken();
        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
        window.setAttributes(lp);
        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
        mOptionsDialog.show();
    }


    public void launchSettings() {
        Intent intent = new Intent();
        intent.setClass(PinyinIME.this, SettingsActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

    private class PopupTimer extends Handler implements Runnable {
        private int mParentLocation[] = new int[2];

        void postShowFloatingWindow() {
            mFloatingContainer.measure(LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT);
            mFloatingWindow.setWidth(mFloatingContainer.getMeasuredWidth());
            mFloatingWindow.setHeight(mFloatingContainer.getMeasuredHeight());
            post(this);
        }

        void cancelShowing() {
            if (mFloatingWindow.isShowing()) {
                mFloatingWindow.dismiss();
            }
            removeCallbacks(this);
        }

        public void run() {
            if(mFloatingWindow == null) return;
            mCandidatesContainer.getLocationInWindow(mParentLocation);

            if (!mFloatingWindow.isShowing()) {
                mFloatingWindow.showAtLocation(mCandidatesContainer,
                        Gravity.LEFT | Gravity.TOP, mParentLocation[0],
                        mParentLocation[1] -mFloatingWindow.getHeight());
            } else {
                mFloatingWindow
                        .update(mParentLocation[0],
                                mParentLocation[1] - mFloatingWindow.getHeight(),
                                mFloatingWindow.getWidth(),
                                mFloatingWindow.getHeight());
            }
        }
    }

    /**
     * Used to notify IME that the user selects a candidate or performs an
     * gesture.
     */
    public class ChoiceNotifier extends Handler implements
            CandidateViewListener {
        PinyinIME mIme;

        ChoiceNotifier(PinyinIME ime) {
            mIme = ime;
        }

        public void onClickChoice(int choiceId) {
            if (choiceId >= 0) {
                mIme.onChoiceTouched(choiceId);
            }
        }

        public void onToLeftGesture() {
            if (ImeState.STATE_IDLE == mImeState) {
                changeToStateInput();
            }
        }

        public void onToRightGesture() {
            if (ImeState.STATE_IDLE == mImeState) {
                changeToStateInput();
            }
        }

        public void onToTopGesture() {
        }

        public void onToBottomGesture() {
        }
    }

    public class OnGestureListener extends
            GestureDetector.SimpleOnGestureListener {
        /**
         * When user presses and drags, the minimum x-distance to make a
         * response to the drag event.
         */
        private static final int MIN_X_FOR_DRAG = 60;

        /**
         * When user presses and drags, the minimum y-distance to make a
         * response to the drag event.
         */
        private static final int MIN_Y_FOR_DRAG = 40;

        /**
         * Velocity threshold for a screen-move gesture. If the minimum
         * x-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_X1 = 0.3f;

        /**
         * Velocity threshold for a screen-move gesture. If the maximum
         * x-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_X2 = 0.7f;

        /**
         * Velocity threshold for a screen-move gesture. If the minimum
         * y-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_Y1 = 0.2f;

        /**
         * Velocity threshold for a screen-move gesture. If the maximum
         * y-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_Y2 = 0.45f;

        /** If it false, we will not response detected gestures. */
        private boolean mReponseGestures;

        /** The minimum X velocity observed in the gesture. */
        private float mMinVelocityX = Float.MAX_VALUE;

        /** The minimum Y velocity observed in the gesture. */
        private float mMinVelocityY = Float.MAX_VALUE;

        /** The first down time for the series of touch events for an action. */
        private long mTimeDown;

        /** The last time when onScroll() is called. */
        private long mTimeLastOnScroll;

        /** This flag used to indicate that this gesture is not a gesture. */
        private boolean mNotGesture;

        /** This flag used to indicate that this gesture has been recognized. */
        private boolean mGestureRecognized;

        public OnGestureListener(boolean reponseGestures) {
            mReponseGestures = reponseGestures;
        }

        @Override
        public boolean onDown(MotionEvent e) {
            mMinVelocityX = Integer.MAX_VALUE;
            mMinVelocityY = Integer.MAX_VALUE;
            mTimeDown = e.getEventTime();
            mTimeLastOnScroll = mTimeDown;
            mNotGesture = false;
            mGestureRecognized = false;
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
            if (mNotGesture) return false;
            if (mGestureRecognized) return true;

            if (Math.abs(e1.getX() - e2.getX()) < MIN_X_FOR_DRAG
                    && Math.abs(e1.getY() - e2.getY()) < MIN_Y_FOR_DRAG)
                return false;

            long timeNow = e2.getEventTime();
            long spanTotal = timeNow - mTimeDown;
            long spanThis = timeNow - mTimeLastOnScroll;
            if (0 == spanTotal) spanTotal = 1;
            if (0 == spanThis) spanThis = 1;

            float vXTotal = (e2.getX() - e1.getX()) / spanTotal;
            float vYTotal = (e2.getY() - e1.getY()) / spanTotal;

            // The distances are from the current point to the previous one.
            float vXThis = -distanceX / spanThis;
            float vYThis = -distanceY / spanThis;

            float kX = vXTotal * vXThis;
            float kY = vYTotal * vYThis;
            float k1 = kX + kY;
            float k2 = Math.abs(kX) + Math.abs(kY);

            if (k1 / k2 < 0.8) {
                mNotGesture = true;
                return false;
            }
            float absVXTotal = Math.abs(vXTotal);
            float absVYTotal = Math.abs(vYTotal);
            if (absVXTotal < mMinVelocityX) {
                mMinVelocityX = absVXTotal;
            }
            if (absVYTotal < mMinVelocityY) {
                mMinVelocityY = absVYTotal;
            }

            if (mMinVelocityX < VELOCITY_THRESHOLD_X1
                    && mMinVelocityY < VELOCITY_THRESHOLD_Y1) {
                mNotGesture = true;
                return false;
            }

            if (vXTotal > VELOCITY_THRESHOLD_X2
                    && absVYTotal < VELOCITY_THRESHOLD_Y2) {
                if (mReponseGestures) onDirectionGesture(Gravity.RIGHT);
                mGestureRecognized = true;
            } else if (vXTotal < -VELOCITY_THRESHOLD_X2
                    && absVYTotal < VELOCITY_THRESHOLD_Y2) {
                if (mReponseGestures) onDirectionGesture(Gravity.LEFT);
                mGestureRecognized = true;
            } else if (vYTotal > VELOCITY_THRESHOLD_Y2
                    && absVXTotal < VELOCITY_THRESHOLD_X2) {
                if (mReponseGestures) onDirectionGesture(Gravity.BOTTOM);
                mGestureRecognized = true;
            } else if (vYTotal < -VELOCITY_THRESHOLD_Y2
                    && absVXTotal < VELOCITY_THRESHOLD_X2) {
                if (mReponseGestures) onDirectionGesture(Gravity.TOP);
                mGestureRecognized = true;
            }

            mTimeLastOnScroll = timeNow;
            return mGestureRecognized;
        }

        @Override
        public boolean onFling(MotionEvent me1, MotionEvent me2,
                float velocityX, float velocityY) {
            return mGestureRecognized;
        }

        public void onDirectionGesture(int gravity) {
            if (Gravity.NO_GRAVITY == gravity) {
                return;
            }

            if (Gravity.LEFT == gravity || Gravity.RIGHT == gravity) {
                if (mCandidatesContainer.isShown()) {
                    if (Gravity.LEFT == gravity) {
                    } else {
                    }
                    return;
                }
            }
        }
    }



    public enum ImeState {
        STATE_IDLE, STATE_INPUT,  STATE_PREDICT
    }

    @Override
    public void onEmojiconClicked(Emojicon emojicon) {
        EmojiconsFragment.input(getCurrentInputConnection(), emojicon);
    }

    public void setEmojiconFragment(boolean useSystemDefault) {
        EmojiconsView emojiconsView = (EmojiconsView) mSkbContainer.findViewById(R.id.emoji_icons);
        if(emojiconsView == null) return;
        if(emojiconsView.mPages != null && emojiconsView.mPages.size() > 0) return;
        emojiconsView.setPages(Arrays.asList(
                new EmojiconPage(Emojicon.TYPE_PEOPLE, null, useSystemDefault, R.drawable.ic_emoji_people_light),
                new EmojiconPage(Emojicon.TYPE_NATURE, null, useSystemDefault, R.drawable.ic_emoji_nature_light),
                new EmojiconPage(Emojicon.TYPE_OBJECTS, null, useSystemDefault, R.drawable.ic_emoji_objects_light),
                new EmojiconPage(Emojicon.TYPE_PLACES, null, useSystemDefault, R.drawable.ic_emoji_places_light),
                new EmojiconPage(Emojicon.TYPE_SYMBOLS, null, useSystemDefault, R.drawable.ic_emoji_symbols_light)
        ),this);
        View emojis_backspace = mSkbContainer.findViewById(R.id.emojis_backspace);
        if(emojis_backspace != null)
        {
            emojis_backspace.setOnClickListener(this);
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId())
        {
            case R.id.emojis_backspace:
                getCurrentInputConnection().deleteSurroundingText(2,0);
                break;
        }
    }
}
